//==============================================================================
//
//            DKey - the (keyboard) key class in the ofc-library
//
//               Copyright (C) 2004  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2008-08-05 16:29:04 $ $Revision: 1.5 $
//
//==============================================================================

#include "ofc/DKey.h"

#include <ctype.h>
#include <string.h>


#if _INTERFACE_

#include "ofc/config.h"

#include "ofc/DText.h"

#include "ofc/DParsable.h"
#include "ofc/DTextable.h"

//
// The key codes
// 
#define DKEY_NULL            (0)
#define DKEY_BACKSPACE       (8)
#define DKEY_ENTER           (13)   // the enter key
#define DKEY_ESCAPE          (27)
#define DKEY_DELETE          (127)

// Function keys
#define DKEY_F1              (282)  // Function key 1
#define DKEY_F2              (283)  // Function key 2
#define DKEY_F3              (284)  // Function key 3
#define DKEY_F4              (285)  // Function key 4
#define DKEY_F5              (286)  // Function key 5
#define DKEY_F6              (287)  // Function key 6
#define DKEY_F7              (288)  // Function key 7
#define DKEY_F8              (289)  // Function key 8
#define DKEY_F9              (290)  // Function key 9
#define DKEY_F10             (291)  // Function key 10
#define DKEY_F11             (292)  // Function key 11
#define DKEY_F12             (293)  // Function key 12
#define DKEY_F13             (294)  // Function key 13
#define DKEY_F14             (295)  // Function key 14
#define DKEY_F15             (296)  // Function key 15
#define DKEY_F16             (297)  // Function key 16
#define DKEY_F17             (298)  // Function key 17
#define DKEY_F18             (299)  // Function key 18
#define DKEY_F19             (300)  // Function key 19
#define DKEY_F20             (301)  // Function key 20

// Modifier keys
#define DKEY_NUMLOCK         (302)  // Numlock key
#define DKEY_CAPSLOCK        (303)  // Capslock key
#define DKEY_SCROLLLOCK      (304)  // Scroll key
#define DKEY_SHIFT           (305)  // Shift key
#define DKEY_CTRL            (306)  // Control key
#define DKEY_ALT             (307)  // Alt key
#define DKEY_KP              (308)  // Keypad key
#define DKEY_MOUSE           (309)  // 'Mouse' key
#define DKEY_MIDDLE          (310)  // Middle key

// Cursor movement
#define DKEY_UP              (273)  // Up key
#define DKEY_DOWN            (274)  // Down key
#define DKEY_RIGHT           (275)  // Right key
#define DKEY_LEFT            (276)  // Left key
#define DKEY_INSERT          (277)  // Insert key
#define DKEY_HOME            (278)  // Home key
#define DKEY_END             (279)  // End key
#define DKEY_PAGEUP          (280)  // Page up key
#define DKEY_PAGEDOWN        (281)  // Page down key

// Modifiers
#define DKEY_MOD_SHIFT       (0x1000)  // the shift modifier
#define DKEY_MOD_CTRL        (0x2000)  // the control modifier
#define DKEY_MOD_ALT         (0x4000)  // the alt modifier
#define DKEY_MOD_KP          (0x8000)  // the keypad modifier


// Masks
#define DKEY_KEY_MASK        (0x0FFF)  // the key mask
#define DKEY_MOD_MASK        (DKEY_MOD_SHIFT|DKEY_MOD_CTRL|DKEY_MOD_ALT|DKEY_MOD_KP) // the default modifier mask

// Mouse keys
#define DKEY_MOUSE_KEYS      (0x0800)  // the mouse keys bit

#define DKEY_MOUSE_RIGHT     (1 | DKEY_MOUSE_KEYS)  // the right mouse button
#define DKEY_MOUSE_MIDDLE    (2 | DKEY_MOUSE_KEYS)  // the middle mouse button
#define DKEY_MOUSE_LEFT      (4 | DKEY_MOUSE_KEYS)  // the left mouse button

//
// The DKey class implements methods for storing keyboard keys. 
// 
// @example
// #include <stdio.h>
// #include "ofc/DKey.h"
// 
// int main(int argc, char *argv[])
// {
//   DKey  *key1 = [DKey alloc];
//   DKey  *key2 = [DKey new  ];
//   DText *str;
// 
//   [key1 init :DKEY_ESCAPE];            // Init with the escape key
// 
//                                        // Check for control key
//   printf("Key1 %s a control key.\n", ([key1 isCtrlKey] ? "is" : "is not"));
// 
//   str = [key1 toText];                 // Convert the key to string description
//   printf("Key1 description: %s.\n", [str cstring]);
//   [str free];
// 
// 
//   [key2 set :DKEY_DELETE|DKEY_MOD_CTRL|DKEY_MOD_ALT]; // Set the key to Ctrl-Alt-Del
// 
//   printf("Key2 %s a control key.\n", ([key2 isCtrlKey ] ? "is" : "is not"));
//   printf("Key2 %s a shift key.\n",   ([key2 isShiftKey] ? "is" : "is not"));
//   printf("Key2 %s a alt key.\n",     ([key2 isAltKey  ] ? "is" : "is not"));
// 
//   str = [key2 toText];                 // Convert the key to string description
//   printf("Key2 description:%s.\n", [str cstring]);
//   [str free];
// 
//   [key1 free];                         // Cleanup
//   [key2 free];
//   
//   return 0;
// }
// 

@interface DKey : DObject <DTextable,DParsable>
{
@private
   int                       _code;   // the keyboard code
}

#endif


@implementation DKey

#if _PRIVATE_

typedef struct
{
  const char *name;
  int         key;
} _Key;

static _Key _keys[] =
{
  { "null",       DKEY_NULL       },
  { "backspace",  DKEY_BACKSPACE  },
  { "enter",      DKEY_ENTER      },
  { "escape",     DKEY_ESCAPE     },
  { "delete",     DKEY_DELETE     },
  { "numlock",    DKEY_NUMLOCK    },
  { "capslock",   DKEY_CAPSLOCK   },
  { "scrolllock", DKEY_SCROLLLOCK },
  { "shift",      DKEY_SHIFT      },
  { "ctrl",       DKEY_CTRL       },
  { "alt",        DKEY_ALT        },
  { "kp",         DKEY_KP         },
  { "mouse",      DKEY_MOUSE      },
  { "up",         DKEY_UP         },
  { "down",       DKEY_DOWN       },
  { "right",      DKEY_RIGHT      },
  { "middle",     DKEY_MIDDLE     },
  { "left",       DKEY_LEFT       },
  { "insert",     DKEY_INSERT     },
  { "home",       DKEY_HOME       },
  { "end",        DKEY_END        },
  { "pageup",     DKEY_PAGEUP     },
  { "pagedown",   DKEY_PAGEDOWN   }
};

#endif


//// Constructors

//
// Initialise an empty key
//
// @return the object
//
- (DKey *) init
{
  [super init];

  _code    = 0;
  
  return self;
}

//
// Initialise a key
// 
// @param code         the key code
// 
// @return the object
// 
- (DKey *) init :(int) code
{
  [self init];

  [self set :code];
  
  return self;
}


//// Destructor

//
// Free the key
//
// @return the object
//
- free
{
  return [super free];
}


//// Member methods

//
// Check if the key is a control key. Note Ctrl-@ till Ctrl-_ are *NOT* 
// control keys.
// 
// @return is it ?
//
- (BOOL) isCtrlKey
{
  return ((_code & DKEY_MOD_CTRL) != 0);
}

//
// Check if the key is a function key
// 
// @return is it ?
// 
- (BOOL) isFunctionKey
{
  int key = (_code & DKEY_KEY_MASK);
  
  return ((key >= DKEY_F1) && (key <= DKEY_F20));
}

//
// Check if the key is a keypad key
// 
// @return is it ?
// 
- (BOOL) isKeypadKey
{
  return ((_code & DKEY_MOD_KP) != 0);
}

//
// Check if the key is an alt key
// 
// @return is it ?
// 
- (BOOL) isAltKey
{
  return ((_code & DKEY_MOD_ALT) != 0);
}

//
// Check if the key is a shifted key
// 
// @return is it ?
// 
- (BOOL) isShiftKey
{
  return ((_code & DKEY_MOD_SHIFT) != 0);
}

//
// Check if the key is a mouse key
// 
// @return is it ?
// 
- (BOOL) isMouseKey
{
  return ((_code & DKEY_MOUSE_KEYS) != 0);
}


//// Main methods

//
// Set the key in the object. (the code is not checked for unknown keys, 
// so 'other' keys can also be stored in the object)
// 
// @param code         the key code 
//
// @return the object
//
- (DKey *) set :(int) code
{
  _code = code;
  
  return self;
}

//
// Get the key code (incl. modifiers) from the object
//  
// @return the key code
//
- (int) get
{
  return _code;
}

//
// Get the key (without the modifiers)
// 
// @return the key
// 
- (int) key
{
  return (_code & DKEY_KEY_MASK);
}

//
// Get the modifiers (without the key, no mouse modifier)
// 
// @return the modifiers
// 
- (int) mod
{
  return (_code & DKEY_MOD_MASK);
}


//// Textable protocol implementation

#if _PRIVATE_

//
// Append a key name to a text object
//
// @param text      the text object
// @param key       the key code
//  
// @return is the name appended ?
// 
static BOOL _appendKey(DText *text, int key)
{
  int i;
  
  for (i = 0; i < sizeof(_keys) / sizeof(_Key); i++)
  {
    if (_keys[i].key == key)
    {
      [text append :_keys[i].name];
      
      return YES;
    }
  }
  
  return NO;
}

#endif

//
// Convert the key to a text string
//
// @return a (new) text string with the key
//
- (DText *) toText
{
  DText *text = [DText new];
  int    key  = [self  key];
  
  if ([self isCtrlKey])
  {  
    _appendKey(text, DKEY_CTRL);
    [text push:'-'];
  }
  if ([self isAltKey])
  {
    _appendKey(text, DKEY_ALT);
    [text push :'-'];
  }
  if ([self isShiftKey])
  {
    _appendKey(text, DKEY_SHIFT);
    [text push :'-'];
  }
  if ([self isKeypadKey])
  {
    _appendKey(text, DKEY_KP);
    [text push :'-'];
  }
  if ([self isMouseKey])
  {
    _appendKey(text, DKEY_MOUSE);
    [text push :'-'];
  }
  
  if ([self isMouseKey])
  {
    BOOL prev = NO;
    
    if ((_code & DKEY_MOUSE_RIGHT) == DKEY_MOUSE_RIGHT)
    {
      _appendKey(text, DKEY_RIGHT);
      
      prev = YES;
    }
    if ((_code & DKEY_MOUSE_MIDDLE) == DKEY_MOUSE_MIDDLE)
    {
      if (prev)
        [text push :'-'];
      
      _appendKey(text, DKEY_MIDDLE);
      
      prev = YES;
    }
    if ((_code & DKEY_MOUSE_LEFT) == DKEY_MOUSE_LEFT)
    {
      if (prev)
        [text push :'-'];
      
      _appendKey(text, DKEY_LEFT);
    }
  }
  else if (!_appendKey(text, key))
  {
    if ((key >= 0) && (key < 32))
    {
      _appendKey(text, DKEY_CTRL);
      
      [text push :'-'];
      [text push :(char) (key+'@')];
    }
    else if ((key >= 32) && (key < 127))
    {
      [text push :(char) key];
    }
    else if ([self isFunctionKey])
    {
      int number = (key - DKEY_F1 + 1);
      
      [text push :'f'];
      
      if ((number / 10) > 0)
      {
        [text push :(char) ((number/10) + '0')];
      }
      [text push :(char) ((number%10) + '0')];
    }
    else
    {
      [text append :"unknown"];
    }
  }

  return text;
}


//// Parsable protocol implementation

//
// Parse a string to set a key object.
// Example: ctrl-alt-delete, shift-f1, ctrl-I, #
//
// @param cstr     the string to be parsed (moved to first non-parsable char)
// 
// @return the result (0, ERANGE, ENODATA)
//
- (int) fromString :(char **) cstr
{
  int   result = ENODATA;
  char *pntr   = *cstr;
  int   code   = 0;
  
  while (isspace(*pntr))
  {
    pntr++;
  }
  
  while ((*pntr != EOS) && (result != ERANGE))
  {
    int i      = 0;
    int number = sizeof(_keys) / sizeof(_Key);

    result = 0;
    
    while (i < number)
    {
      int length = strlen(_keys[i].name);
    
      if (strncasecmp(_keys[i].name, pntr, length) == 0)
      {
        pntr += length;
      
        break;
      }
      
      i++;
    }
    
    if (i < number)
    {
      if ((*pntr == '-') || (*pntr == '+'))
      {
        pntr++;
        
        switch (_keys[i].key)
        {
        case DKEY_SHIFT: 
          code |= DKEY_MOD_SHIFT; 
          break;
          
        case DKEY_CTRL: 
          code |= DKEY_MOD_CTRL;  
          break; 
          
        case DKEY_ALT: 
          code |= DKEY_MOD_ALT;   
          break;
          
         case DKEY_KP: 
          code |= DKEY_MOD_KP;    
          break; 
          
        case DKEY_MOUSE: 
          code |= DKEY_MOUSE_KEYS; 
          break;
        
         case DKEY_LEFT: 
          if ((code & DKEY_MOUSE_KEYS) != 0) 
            code |= DKEY_MOUSE_LEFT;
          else
            result = ERANGE;
          break;
          
        case DKEY_MIDDLE: 
          if ((code & DKEY_MOUSE_KEYS) != 0)
            code |= DKEY_MOUSE_MIDDLE;
          else
            result = ERANGE;
          break;
          
        case DKEY_RIGHT:
          if ((code & DKEY_MOUSE_KEYS) != 0)
            code |= DKEY_MOUSE_RIGHT;
          else
            result = ERANGE;
          break;
          
        default: 
          result = ERANGE;
          break;
        }
      }
      else
      {
        int key = _keys[i].key;
          
        switch(key)
        {
         case DKEY_LEFT: 
          if ((code & DKEY_MOUSE_KEYS) != 0) 
            code |= DKEY_MOUSE_LEFT;
          else
            code |= key;
          break;
          
        case DKEY_MIDDLE : 
          if ((code & DKEY_MOUSE_KEYS) != 0)
            code |= DKEY_MOUSE_MIDDLE;
          else
            code |= key;
          break;
          
        case DKEY_RIGHT  :
          if ((code & DKEY_MOUSE_KEYS) != 0)
            code |= DKEY_MOUSE_RIGHT;
          else
            code |= key;
          break;
          
        default:
          code |= key;
        }

        break;
      }
    }
    else if (((*pntr == 'f') || (*pntr == 'F')) && (isdigit(*(pntr+1))))
    {
      int key  = 0;
      
      pntr++;
      
      key = (int) (*pntr++ - '0');
      
      if (isdigit(*pntr))
      {
        key = key * 10 + (int)(*pntr++ - '0');
      }
        
      if ((key >= 1) && (key <= 20))
      {
        code |= (key - 1 + DKEY_F1);
        
        break;
      }
      else
      {
        result = ERANGE;
      }
    }
    else if (isprint(*pntr))
    {
      int key   = *pntr++;
      
      int upper = toupper(key);
      
      // Translate Ctrl-@ .. to NULL..
      if (((code & DKEY_MOD_CTRL) != 0) && ((upper >= '@') && (upper <= '_')))
      {
        code &= (~DKEY_MOD_CTRL);
        
        code |= (upper - '@');
      }
      else
      {
        code |= key;
      }
      break;
    }
    else
    {
      result = ERANGE;
    }
  }
  
  if (result == 0)
  {
    *cstr = pntr;
    
    _code = code;
  }
  
  return result;
}

@end

/*==========================================================================*/
